﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Driver;
using Sustainalytics.ClientDW.Entities;
using Sustainalytics.Entities.ESGRatings;
using Sustainalytics.ESG.Entities;
using Sustainalytics.ESGComputing.Service.Infrastructure;
using Sustainalytics.ESGComputing.Service.Mappers;
using Sustainalytics.ESGRatings.BusinessLogic.Computing;
using Sustainalytics.ESGRatings.Computing.Entities;
using Sustainalytics.ESGRatings.Computing.Entities.ESG;
using Sustainalytics.ESGRatings.DataContract;
using Sustainalytics.ESGRatings.DataContract.ESG;
using Sustainalytics.ESGRatings.DataContract.Mappers;
using Sustainalytics.ESGRatings.DataLayer;
using Sustainalytics.MongoDB.Extensions;

namespace Sustainalytics.ESGComputing.ProfileTest
{
    class Program
    {
        private static ESGSetLoader _dataLoader;
        private static ComputationEngine _computationEngine;
        private static string _connectionString;

        static void Main(string[] args)
        {

            var filter = (byte)ESGRatingsOptions.CompanyAttributesEnum.Public;
            var flag = 0;
            var attribute1 = (byte)(ESGRatingsOptions.CompanyAttributesEnum.TypeA | ESGRatingsOptions.CompanyAttributesEnum.Public);
            var attribute2 = attribute1 & filter;



            _connectionString = "mongodb://54.171.12.91:27017/ClientDW_A";
            var dataSetConnectionString = "mongodb://54.171.12.91:27017/RPHistoricalDataSet";

            var log = new Dictionary<string, Exception>();

            _dataLoader = new ESGSetLoader(
                    MongoFactory.GetCollectionFromDatabase<HistoricalDataSet>(dataSetConnectionString),
                    MongoFactory.GetCollectionFromDatabase<DSCorporateData>(dataSetConnectionString),
                    MongoFactory.GetCollectionFromDatabase<DSCompanyRawScores>(dataSetConnectionString),
                    MongoFactory.GetCollectionFromDatabase<DSIndicatorTemplate>(dataSetConnectionString),
                    MongoFactory.GetCollectionFromDatabase<DSWeightMatrix>(dataSetConnectionString),
                    MongoFactory.GetCollectionFromDatabase<DSReferenceUniverse>(dataSetConnectionString));

            _computationEngine = ComputationEngine.Instance((string template, Exception ex) => { log.Add(template, ex); });


            var options = new ESGRatingsOptionsDto
            {
                //MatrixId = new Guid("00000000-0000-0000-0000-000000000000"), // Default Weight Matrix
                MatrixId = new Guid("68ed7cbc-6938-4dcc-b84d-acc47d6fb81e"), // STOXX
                //RefUnivId = new Guid("71f2972e-3153-4f83-ac6e-d82bd676481f"), // Default
                RefUnivId = new Guid("8f00443c-d944-44c5-910d-2afa6c1eca04"), // STOXX Global 1800

                //CompaniesIds = new List<Guid>() { new Guid("6c6845ff-4d9e-4f4d-b920-962af834ef0a") }, //Chevron
                CompaniesIds = new List<Guid>()
                {
                    new Guid("dac0eb43-299d-4e2a-99c0-77e23df7eb48"), // "Dechra Pharmaceuticals PLC"
                    new Guid("aaa077ee-d037-4a75-8ea6-4a6bd5336b26"), // "OSG Corporation"
                    new Guid("0962cd9e-0984-4177-a437-77338d25d26b"), // "Nippon Shokubai Co., Ltd."
                },

                GroupBy = ComparativeGroupEnum.PeerGroup,
                CompanyType = CompanyTypeEnum.Public, // CompanyTypeEnum.Public & CompanyTypeEnum.Private
                TemplateType = TemplateTypeEnum.Combined,// TemplateTypeEnum.TypeA & TemplateTypeEnum.TypeB
                IncludeAggregatedScores = true,
                IncludeRatings = true,
                IncludeWeights = true,
                IncludeTopPerformersInfo = false,
                IncludeIndWeightedScores = true,
                IncludeAggregatedWeights = true,
                IncludeCompaniesInGroup = true,
            };

            PrepareData(options);



            var computingOptions = options.ToDomainModel();


            Stopwatch watch = new Stopwatch();
            watch.Start();

            var cudaResult = _computationEngine.ComputeESGRatings(computingOptions);

            watch.Stop();
            Console.Write($"buildcontract<Indicators> {watch.ElapsedMilliseconds}");

            watch.Reset();
            watch.Start();


            var result = _computationEngine.ComputeESGRatings(computingOptions);

            CheckResult(cudaResult, result);

        }


        private static void CheckResult(ESGComputeResult cudaResult, ESGComputeResult result)
        {
            for (int i = 0; i < cudaResult.Ratings.CompanyIndices.Length; i++)
            {
                var companyIndice = cudaResult.Ratings.CompanyIndices[i];
                var cudaRating = cudaResult.Ratings.Ratings[i];

                var index = 0;
                for (int j = 0; j < result.Ratings.CompanyIndices.Length; j++)
                {
                    if (result.Ratings.CompanyIndices[j] == companyIndice)
                    {
                        index = j;
                        break;
                    }
                }
                var rating = result.Ratings.Ratings[index];
                

                if (Math.Round(cudaRating, 3) != Math.Round(rating, 3))
                {
                    throw new Exception("different rating");
                }

                var cudaRanking = cudaResult.Ratings.Rankings[i];
                var ranking = result.Ratings.Rankings[i];

                if (cudaRanking != ranking)
                {
                    throw new Exception("different ranking");
                }

                var cudaPercentile = cudaResult.Ratings.Percentile[i];
                var percentile = result.Ratings.Percentile[i];

                if (cudaPercentile != percentile)
                {
                    throw new Exception("different percentile");
                }

                var cudaGroup_Avgs = cudaResult.Ratings.Group_Avgs[i];
                var group_Avgs = result.Ratings.Group_Avgs[i];

                if (Math.Round(cudaGroup_Avgs, 0) != Math.Round(group_Avgs, 0))
                {
                    throw new Exception("different group_Avgs");
                }

                var cudaGroup_Maxs = cudaResult.Ratings.Group_Maxs[i];
                var group_Maxs = result.Ratings.Group_Maxs[i];

                if (Math.Round(cudaGroup_Maxs, 0) != Math.Round(group_Maxs, 0))
                {
                    //throw new Exception("different group_Maxs");
                }

                var cudaGroup_Mins = cudaResult.Ratings.Group_Mins[i];
                var group_Mins = result.Ratings.Group_Mins[i];

                if (Math.Round(cudaGroup_Mins, 0) != Math.Round(group_Mins, 0))
                {
                    throw new Exception("different group_Mins");
                }
            }
        }


        private static ESGRatingsDto GetCudaRatings(ESGRatingsOptions computingOptions, ESGRatingsOptionsDto options)
        {
            ESGRatingsDto dto;

            using (var result = _computationEngine.ComputeESGRatings(computingOptions))
            {

                ICompanyFilter filter = GetFilter(options, _computationEngine);
                var utils = new ESGComputeMap(result, filter);

                dto = new ESGRatingsDto
                {
                    IndicatorsGroups = utils.ToIndicatorsGroupsDto(),
                    SetId = result.SetId,
                };

                if (options.IncludeIndWeightedScores)
                {
                    dto.IndicatorsCodes = result.Configuration.Codes.ToArray();
                    dto.Indicators = utils.GetIndicators().ToList();
                }


                if (options.IncludeRatings)
                {
                    dto.Ratings = utils.GetRatings().ToList();
                }

                if (options.IncludeAggregatedScores && !options.IncludeTopPerformersInfo)
                {
                    dto.Groups = utils.GetGroups().ToArray();
                }

                if (options.IncludeAggregatedScores && options.IncludeTopPerformersInfo)
                {
                    dto.Groups = utils.GetGroupsWithTopCompanies().ToArray();
                }
            }

            return dto;
        }


        static Guid ReferenceUniversesId = new Guid("00000000-0000-0000-0000-000000000002");

        static void PrepareData(ESGRatingsOptionsDto options)
        {
            var esgDataContainer = new ESGDataContainer();

            esgDataContainer.IndicatorTemplates = GetIndicatorTemplates();
            esgDataContainer.Companies = GetCorporateData(options).ToArray();
            esgDataContainer.CompaniesScores = GetIndicatorsData(options);


            var matrixWeights = from weight in GetCompanyWeightsData(options)
                                select
                                    new ESGRatings.Computing.Entities.ESG.CompanyWeights
                                    {
                                        CompanyId = weight.CompanyId,
                                        ProfileHasEsgResearch = weight.ProfileHasEsgResearch,
                                        IndicatorsData = weight.IndicatorsData.Select(x => new Weight(x.Code, x.Value))
                                    };

            esgDataContainer.Matrices = new[]
            {
                new ESGRatings.Computing.Entities.ESG.WeightMatrix
                {
                    AccountId = Guid.NewGuid(),
                    MatrixId = options.MatrixId,
                    CompaniesWeights = matrixWeights.ToArray()
                }
            };

            esgDataContainer.ReferenceUniverses = new[] { GetReferenceUniverse(options) };

            Guid? setId = null; // new Guid("0ff94cba-70eb-41bd-9a64-ee2bd1118cf6");


            var data = _dataLoader.GetDataFromDatabase(setId).Result;
            //data.IsActive = set.isActive;

            //Guid id = LoadSetToEngine(data);

            Guid id = LoadSetToEngine(esgDataContainer);

        }


        private static Guid LoadSetToEngine(ESGDataContainer data)
        {
            //ICollection<string> messages;
            //if (data.Validate(out messages))
            //{
            //    throw new InvalidInput(messages.Aggregate((i, j) => i + " " + j));
            //}

            var id = _computationEngine.AddESGSet(
                data.SetId,
                data.Companies.ToDomainModel().ToList(),
                data.IndicatorTemplates,
                data.CompaniesScores,
                data.Matrices,
                data.ReferenceUniverses,
                data.DefaultMatrixId,
                data.IsActive);

            return id;
        }

        private static string GetClusterCode(string indicatorCategory)
        {
            var code = indicatorCategory.Substring(0, 1);

            if (indicatorCategory == "Quantitative Performance")
                code = "QNP";

            if (indicatorCategory == "Qualitative Performance - Controversies")
                code = "QLP";

            return code;
        }

        private static ESGRatings.Computing.Entities.IndicatorTemplate[] GetIndicatorTemplates()
        {
            var indicatorTemplates = MongoFactory.GetCollectionFromDatabase<ESG.Entities.IndicatorTemplate>(_connectionString)
                .Find(new BsonDocument())
                .ToEnumerable()
                .OrderBy(x => x.ThemeSortKey)
                .Select(x => new ESGRatings.Computing.Entities.IndicatorTemplate
                {
                    Code = x.Number,
                    Theme = x.ThemeName.Substring(0, 1),
                    Cluster = GetClusterCode(x.IndicatorCategory)
                })
                .ToArray();


            return indicatorTemplates;
        }

        private static ICollection<CompanyData> GetCorporateData(ESGRatingsOptionsDto options)
        {
            var companies = MongoFactory.GetCollectionFromDatabase<CorporateData>(_connectionString)
                .Find(x => options.CompaniesIds.Contains(x.Id))
                .Project(x => new CompanyData
                {
                    Id = x.Id,
                    CompanyType = x.CompanyType,
                    TemplateType = x.TemplateType,
                    IndustryGroup = x.PeerGroup,
                    PeerGroup = x.SubPeerGroup
                })
                .ToList();

            return companies;
        }

        private static CompanyScores[] GetIndicatorsData(ESGRatingsOptionsDto options)
        {
            var companyScores = new ConcurrentBag<CompanyScores>();

            Parallel.ForEach(options.CompaniesIds,
                new ParallelOptions
                {
                    MaxDegreeOfParallelism = Convert.ToInt32(Math.Ceiling(Environment.ProcessorCount * 0.75))
                },
                companyId =>
                {
                    var indicators = MongoFactory.GetCollectionFromDatabase<RPIndicatorExtended>(_connectionString)
                      .Find(x => x.CompanyId == companyId)
                      .Project(x => new IndicatorDefaultScore(x.Number, x.SelectedAnswer.Score, (float?)x.Weight.Value, (float?)x.WeightedScore.Value))
                      .ToList();

                    companyScores.Add(new CompanyScores(companyId, indicators));
                });

            return companyScores.ToArray();
        }

        private static ESGRatings.Computing.Entities.ESG.CompanyWeights[] GetCompanyWeightsData(ESGRatingsOptionsDto options)
        {
            var companyWeights = new ConcurrentBag<ESGRatings.Computing.Entities.ESG.CompanyWeights>();

            Parallel.ForEach(options.CompaniesIds,
                new ParallelOptions
                {
                    MaxDegreeOfParallelism = Convert.ToInt32(Math.Ceiling(Environment.ProcessorCount * 0.75))
                },
                companyId =>
                {
                    var weights = MongoFactory.GetCollectionFromDatabase<RPIndicatorExtended>(_connectionString)
                        .Find(x => x.CompanyId == companyId)
                        .Project(x => new Weight(x.Number, (float)x.Weight.Value))
                        .ToList();

                    companyWeights.Add(new ESGRatings.Computing.Entities.ESG.CompanyWeights(companyId, weights.ToArray()));
                });

            return companyWeights.ToArray();
        }

        private static ESGRatings.Computing.Entities.ESG.ReferenceUniverse GetReferenceUniverse(ESGRatingsOptionsDto options)
        {
            var referenceUniverse = MongoFactory.GetCollectionFromDatabase<Entities.ESGRatings.ReferenceUniverse>(_connectionString)
                .Find(x => x.Id == options.RefUnivId)
                .Project(x => new ESGRatings.Computing.Entities.ESG.ReferenceUniverse
                {
                    Id = x.Id,
                    Companies = x.Companies.Select(c => new ESGRatings.Computing.Entities.ESG.ReferenceUniverseCompany
                    {
                        CapitalIQID = c.CapitalIqId.ToString(),
                        CompanyId = c.CompanyId,
                        CompanyName = c.CompanyName
                    })
                    .ToArray()
                })
                .SingleOrDefault();

            return referenceUniverse;



            //return new ESGRatings.Computing.Entities.ESG.ReferenceUniverse
            //{
            //    Id = ReferenceUniversesId,
            //    Companies = new[]
            //    {
            //        new ESGRatings.Computing.Entities.ESG.ReferenceUniverseCompany { CapitalIQID = "IQ20704112", CompanyId = CompanyIds[0], CompanyName = "Compania1" },
            //        new ESGRatings.Computing.Entities.ESG.ReferenceUniverseCompany { CapitalIQID = "IQ20704113", CompanyId = CompanyIds[1], CompanyName = "Compania2" },
            //        new ESGRatings.Computing.Entities.ESG.ReferenceUniverseCompany { CapitalIQID = "IQ20704114", CompanyId = CompanyIds[2], CompanyName = "Compania3" },
            //        new ESGRatings.Computing.Entities.ESG.ReferenceUniverseCompany { CapitalIQID = "IQ20704115", CompanyId = CompanyIds[3], CompanyName = "Compania4" },
            //        new ESGRatings.Computing.Entities.ESG.ReferenceUniverseCompany { CapitalIQID = "IQ20704116", CompanyId = CompanyIds[4], CompanyName = "Compania5" },
            //        new ESGRatings.Computing.Entities.ESG.ReferenceUniverseCompany { CapitalIQID = "IQ20704117", CompanyId = CompanyIds[5], CompanyName = "Compania6" },
            //        new ESGRatings.Computing.Entities.ESG.ReferenceUniverseCompany { CapitalIQID = "IQ20704118", CompanyId = CompanyIds[6], CompanyName = "Compania7" },
            //        new ESGRatings.Computing.Entities.ESG.ReferenceUniverseCompany { CapitalIQID = "IQ20704119", CompanyId = CompanyIds[7], CompanyName = "Compania8" },
            //        new ESGRatings.Computing.Entities.ESG.ReferenceUniverseCompany { CapitalIQID = "IQ207041110", CompanyId = CompanyIds[8], CompanyName = "Compania9" },
            //        new ESGRatings.Computing.Entities.ESG.ReferenceUniverseCompany { CapitalIQID = "IQ207041111", CompanyId = CompanyIds[9], CompanyName = "Compania10" },

            //    }
            //};
        }



        // todo: should construct a separate factory for getting the filter in order to better test logic
        private static ICompanyFilter GetFilter(ESGRatingsOptionsDto options, ComputationEngine engine)
        {
            if (options.CompaniesIds != null && options.CompaniesIds.Count > 0)
            {
                var additionalCompaniesIds = new List<Guid>();
                if (options.IncludeCompaniesInGroup)
                {
                    foreach (var companyId in options.CompaniesIds)
                    {
                        var companiesToAdd = engine.GetCompaniesInComparativeGroup(
                            companyId,
                            options.MatrixId,
                            options.RefUnivId,
                            options.GroupBy,
                            options.SetId);
                        if (companiesToAdd != null)
                        {
                            additionalCompaniesIds.AddRange(companiesToAdd);
                        }
                    }
                }

                additionalCompaniesIds.AddRange(
                    engine.GetMatchingCompanies(
                        options.MatrixId,
                        options.RefUnivId,
                        options.PeerGroups,
                        options.IndustryGroups, options.SetId));
                return new CompanyFilter(options.CompaniesIds.Union(additionalCompaniesIds));
            }

            if (options.IndustryGroups == null || options.IndustryGroups.Count == 0)
            {
                if ((options.PeerGroups == null) || options.PeerGroups.Count == 0)
                {
                    return null;
                }
            }

            return
                new CompanyFilter(
                    engine.GetMatchingCompanies(
                        options.MatrixId,
                        options.RefUnivId,
                        options.PeerGroups,
                        options.IndustryGroups, options.SetId));
        }

    }
}
